﻿using Dyao.Book.Contract;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;


namespace Dyao.Book.Web.Extensions
{
    /// <summary>
    /// 查询参数过滤
    /// </summary>
    public class QueryParamsFilterAttribute : ActionFilterAttribute
    {
        //缓存字典：Key-Controller中每个方法的唯一ID；Value-针对方法中每个参数的各种处理过程集合体
        protected static readonly ConcurrentDictionary<string, ControllerActionParameterAnalysisResultModel> _cacheActionParameterAnalysisResultDict = new ConcurrentDictionary<string, ControllerActionParameterAnalysisResultModel>();

        //处理日期参数 -- 日期类型列表
        protected static readonly Type[] _datetimePropTypes = new Type[] { typeof(DateTime), typeof(DateTime?) };

        //处理日期参数 -- 缓存字典：Key-Model类型；Value-解析Model中的属性信息：包含标记为 DateFilterStartAttribute 与 DateFilterEndAttribute 的属性信息集合
        protected static readonly ConcurrentDictionary<Type, DateFilterPropertyAnalysisResultModel> _cacheClassDatePropertyAnalysisResultDict = new ConcurrentDictionary<Type, DateFilterPropertyAnalysisResultModel>();

        //处理日期参数 -- 缓存字典：Key-Model类型；Value-对指定Model中特定日期属性值的处理过程
        protected static readonly ConcurrentDictionary<Type, Action<object>> _cacheClassDatePropertyValueHandlerDict = new ConcurrentDictionary<Type, Action<object>>();

        /// <inheritdoc cref="ActionFilterAttribute.OnActionExecuting(ActionExecutingContext)"/>
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var analysisInfo = getActionParameterAnalysisResultCache(context.ActionDescriptor.Id, context.ActionDescriptor.Parameters);
            if (!analysisInfo.IsExistsHandler) return;

            foreach (var handler in analysisInfo.DictHandler)
            {
                var value = context.ActionArguments.GetOrDefault(handler.Key);
                if (value is null) continue;

                foreach (Action<object> action in handler.Value.ClassPropertyHandlers)
                {
                    action(value);
                }

                if (handler.Value.DatetimeParameterHandler != null)
                {
                    var newValue = handler.Value.DatetimeParameterHandler(value);
                    context.ActionArguments[handler.Key] = newValue;
                }
            }

            base.OnActionExecuting(context);
        }

        /// <summary>
        /// 获取控制器方法中对各参数的处理方法解析结果缓存数据
        /// </summary>
        /// <param name="actionId">控制器中方法的ID</param>
        /// <param name="parameters">方法参数描述信息</param>
        /// <returns></returns>
        protected static ControllerActionParameterAnalysisResultModel getActionParameterAnalysisResultCache(string actionId, IList<ParameterDescriptor> parameters)
        {
            return _cacheActionParameterAnalysisResultDict.GetOrAdd(actionId, () =>
            {
                var analysisInfo = new ControllerActionParameterAnalysisResultModel() { DictHandler = new Dictionary<string, ControllerActionParameterHandlerModel>() };

                foreach (ControllerParameterDescriptor parameter in parameters.OfType<ControllerParameterDescriptor>())
                {
                    var classPropertyHandlers = new List<Action<object>>();
                    Func<object, object> datetimeParameterHandler = null;

                    //参数类型是 Class
                    if (parameter.ParameterType.IsClass)
                    {
                        var fun = getDatePropertyValueHandler(parameter.ParameterType);
                        if (fun != null) classPropertyHandlers.Add(fun);
                    }
                    //参数类型是 DateTime
                    else if (_datetimePropTypes.Contains(parameter.ParameterType))
                    {
                        var fun = HandleDateParamOfDatetime(parameter.ParameterInfo);
                        if (fun != null) datetimeParameterHandler = fun;
                    }
                    else { }

                    analysisInfo.DictHandler.Add(parameter.Name, new ControllerActionParameterHandlerModel()
                    {
                        ClassPropertyHandlers = classPropertyHandlers,
                        DatetimeParameterHandler = datetimeParameterHandler,
                    });
                }

                analysisInfo.IsExistsHandler = analysisInfo.DictHandler.Any(it => it.Value.ClassPropertyHandlers.Any() || it.Value.DatetimeParameterHandler != null);
                return analysisInfo;
            });
        }

        #region == 处理日期参数 ==

        /// <summary>
        /// 从缓存中获取一个类型属性解析结果
        /// <para>解析Model中的属性信息：包含标记为 <see cref="DateFilterStartAttribute"/> 与 <see cref="DateFilterEndAttribute"/> 的属性信息集合</para>
        /// </summary>
        protected static DateFilterPropertyAnalysisResultModel getPropertyAnalysisInfoForDate(Type modelType)
        {
            return _cacheClassDatePropertyAnalysisResultDict.GetOrAdd(modelType, t =>
            {
                var propertyInfos = t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                     .Where(it => it.CanRead && it.CanWrite)
                                     .Where(it => _datetimePropTypes.Contains(it.PropertyType));

                var startDatePropertyInfos = propertyInfos.Where(it => it.GetCustomAttribute<DateFilterStartAttribute>(true) != null);
                var endDatePropertyInfos = propertyInfos.Where(it => it.GetCustomAttribute<DateFilterEndAttribute>(true) != null);

                return new DateFilterPropertyAnalysisResultModel()
                {
                    IsAnyDateFilterProperty = startDatePropertyInfos.Any() || endDatePropertyInfos.Any(),
                    StartDatePropertyInfos = startDatePropertyInfos.ToArray(),
                    EndDatePropertyInfos = endDatePropertyInfos.ToArray(),
                };
            });
        }

        /// <summary>
        /// 从缓存中获取一个方法，传递一个类型实例，对其数据的指定日期类型属性值做特殊处理
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns></returns>
        protected static Action<object> getDatePropertyValueHandler(Type type)
        {
            return _cacheClassDatePropertyValueHandlerDict.GetOrAdd(type, t =>
            {
                return HandleDateParamOfClassProperty(t);
            });
        }

        /// <summary>
        /// 获取一个方法，传递一个类型实例，对其数据的指定日期类型属性值做特殊处理
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns></returns>
        protected static Action<object> HandleDateParamOfClassProperty(Type type)
        {
            var analysisInfo = getPropertyAnalysisInfoForDate(type);
            if (!analysisInfo.IsAnyDateFilterProperty) return null;

            var modelExpr = Expression.Parameter(typeof(object), "model");
            var tmpModelExpr = Expression.Variable(type, "tmpModel");
            var tmpModelAssignExpr = Expression.Assign(tmpModelExpr, Expression.Convert(modelExpr, type));

            var nullExpr = Expression.Constant(null);
            var defDTimeExpr = Expression.Constant(default(DateTime), typeof(DateTime));

            var processBlocks = new List<Expression>() { tmpModelAssignExpr };

            foreach (var property in analysisInfo.StartDatePropertyInfos)
            {
                var modelPropertyExpr = Expression.Property(tmpModelExpr, property);

                MemberExpression dtimeExpr = modelPropertyExpr;
                var isNullable = property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>);
                if (isNullable) dtimeExpr = Expression.MakeMemberAccess(modelPropertyExpr, property.PropertyType.GetProperty("Value"));
                var curDateExpr = Expression.MakeMemberAccess(dtimeExpr, typeof(DateTime).GetProperty(nameof(DateTime.Date)));

                var dtimeAssignExpr = Expression.Assign(modelPropertyExpr, Expression.Convert(curDateExpr, property.PropertyType));
                var notNullOrDefaultExpr = Expression.NotEqual(modelPropertyExpr, isNullable ? nullExpr : defDTimeExpr);
                var block = Expression.IfThen(notNullOrDefaultExpr, Expression.Block(dtimeAssignExpr));

                processBlocks.Add(block);
            }

            foreach (var property in analysisInfo.EndDatePropertyInfos)
            {
                var modelPropertyExpr = Expression.Property(tmpModelExpr, property);

                MemberExpression dtimeExpr = modelPropertyExpr;
                var isNullable = property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>);
                if (isNullable) dtimeExpr = Expression.MakeMemberAccess(modelPropertyExpr, property.PropertyType.GetProperty("Value"));
                var curDateExpr = Expression.MakeMemberAccess(dtimeExpr, typeof(DateTime).GetProperty(nameof(DateTime.Date)));

                var addDaysMethod = typeof(DateTime).GetMethod(nameof(DateTime.AddDays));
                Expression newDtimeExpr = Expression.Call(curDateExpr, addDaysMethod, Expression.Constant(1d, typeof(double)));
                var isLastSecondOfToday = property.GetCustomAttribute<DateFilterEndAttribute>(true)?.IsLastSecondOfToday ?? false;
                if (isLastSecondOfToday)
                {
                    var addSecondsMethod = typeof(DateTime).GetMethod(nameof(DateTime.AddSeconds));
                    newDtimeExpr = Expression.Call(newDtimeExpr, addSecondsMethod, Expression.Constant(-1d, typeof(double)));
                }

                var dtimeAssignExpr = Expression.Assign(modelPropertyExpr, Expression.Convert(newDtimeExpr, property.PropertyType));
                var notNullOrDefaultExpr = Expression.NotEqual(modelPropertyExpr, isNullable ? nullExpr : defDTimeExpr);
                var block = Expression.IfThen(notNullOrDefaultExpr, Expression.Block(dtimeAssignExpr));

                processBlocks.Add(block);
            }

            var body = Expression.IfThen(Expression.NotEqual(modelExpr, nullExpr), Expression.Block(new ParameterExpression[] { tmpModelExpr }, processBlocks));

            var lambda = Expression.Lambda<Action<object>>(body, modelExpr);
            return lambda.Compile();
        }

        /// <summary>
        /// 获取一个方法，传递一个方法的参数信息，返回对参数值处理后的结果
        /// </summary>
        /// <param name="parameterInfo">参数信息</param>
        /// <returns></returns>
        protected static Func<object, object> HandleDateParamOfDatetime(ParameterInfo parameterInfo)
        {
            var startDateAttributes = parameterInfo.GetCustomAttributes<DateFilterStartAttribute>(true);
            var endDateAttributes = parameterInfo.GetCustomAttributes<DateFilterEndAttribute>(true);
            if ((!startDateAttributes.Any()) && (!endDateAttributes.Any())) return null;

            Type type = parameterInfo.ParameterType;
            var isNullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);

            var dtimeExpr = Expression.Parameter(typeof(object), "dtime");
            var tmpDtimeExpr = Expression.Variable(type, "tmpDtime");
            var tmpDtimeAssignExpr = Expression.Assign(tmpDtimeExpr, Expression.Convert(dtimeExpr, type));

            var labelTarget = Expression.Label(typeof(object), "end");
            var nullExpr = Expression.Constant(null);
            var defDTimeExpr = Expression.Constant(default(DateTime), typeof(DateTime));

            Expression dateMemberExpr = isNullable ? (Expression)Expression.MakeMemberAccess(tmpDtimeExpr, type.GetProperty("Value")) : tmpDtimeExpr;
            Expression resultDateExpr = Expression.MakeMemberAccess(dateMemberExpr, typeof(DateTime).GetProperty(nameof(DateTime.Date)));

            if (startDateAttributes.Any())
            {
            }
            else if (endDateAttributes.Any())
            {
                var isLastSecondOfToday = endDateAttributes.FirstOrDefault()?.IsLastSecondOfToday ?? false;

                var addDaysMethod = typeof(DateTime).GetMethod(nameof(DateTime.AddDays));
                Expression newDtimeExpr = Expression.Call(resultDateExpr, addDaysMethod, Expression.Constant(1d, typeof(double)));
                if (isLastSecondOfToday)
                {
                    var addSecondsMethod = typeof(DateTime).GetMethod(nameof(DateTime.AddSeconds));
                    newDtimeExpr = Expression.Call(newDtimeExpr, addSecondsMethod, Expression.Constant(-1d, typeof(double)));
                }

                resultDateExpr = newDtimeExpr;
            }
            else { }

            var labelReturnExpr = Expression.Return(labelTarget, Expression.Convert(resultDateExpr, typeof(object)));
            var notNullOrDefaultExpr = Expression.NotEqual(tmpDtimeExpr, isNullable ? nullExpr : defDTimeExpr);

            var body = Expression.Block(new ParameterExpression[] {
                    tmpDtimeExpr
                }, new Expression[] {
                    tmpDtimeAssignExpr,
                    Expression.IfThen(notNullOrDefaultExpr, labelReturnExpr),
                    Expression.Label(labelTarget, dtimeExpr),
                });

            var lambda = Expression.Lambda<Func<object, object>>(body, dtimeExpr);
            return lambda.Compile();
        }

        #endregion

        /// <summary>
        /// 方法中每个参数的各种处理过程集合体
        /// </summary>
        protected class ControllerActionParameterAnalysisResultModel
        {
            /// <summary>
            /// 是否存在指定参数需要处理的方法
            /// </summary>
            public bool IsExistsHandler { get; set; }

            /// <summary>
            /// 针对方法中每个参数的各种处理过程集合
            /// <para>Key：控制器中方法参数名称</para>
            /// <para>Value：包含对参数的各种处理方法</para>
            /// </summary>
            public IDictionary<string, ControllerActionParameterHandlerModel> DictHandler { get; set; }
        }

        /// <summary>
        /// 方法中对参数的各种处理过程
        /// </summary>
        protected class ControllerActionParameterHandlerModel
        {
            /// <summary>
            /// 如果参数是自定义类型（Class），则为对整个参数对象的处理程序集合
            /// </summary>
            public IReadOnlyList<Action<object>> ClassPropertyHandlers { get; set; }

            /// <summary>
            /// 如果参数是日期类型（DateTime），则处理程序对其处理后应返回新的值
            /// </summary>
            public Func<object, object> DatetimeParameterHandler { get; set; }
        }
    }
}
